package com.softwareengineering.trafficapplicationv2;
import java.util.Vector;

import android.graphics.Canvas;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.SurfaceHolder;

import com.softwareengineering.trafficapplicationv2.MicroStreet.ScoreChangeListener;

/**
 * This file is part of the Traffic Simulation Application.
 * The Traffic Simulation Application is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * The Traffic Simulation Application is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with the Traffic Simulation Application.  If not, see <http://www.gnu.org/licenses/>.
 *  
 * @file SimThread.java
 * @author	(c) Martin Treiber, http://traffic-simulation.de/
 * @alterations David Black, Chris Nelson, and Garrett Stibbs, (c) December 03, 2012
 * @description Extended Thread class responsible for drawing and maintenance
 * 				of game
 * @note Contains custom classes for positioning of various display elements
 * 
 */

class SimThread extends Thread implements Constants, ScoreChangeListener {

	//	##################################
	//		SimThread Member Variables
	//	##################################

	// display metric variables
	static final int VEH_COLORCODE = 1; // specify desired vehicle color code -> 0: own colors; 1: speed color code; 2: VLA code
	private static int mCanvasHeight = 1;	// canvas height
	private static int mCanvasWidth = 1;	// canvas width
	private static int xsize; // width, height of window in pixels
	private static int ysize; // y pixel region of circle or U w/o on-ramp
	private static double scale; // scale factor pixels/m
	private static double scalex;	// x-scale factor
	private static double scaley;	// y-scale factor
	private static double rdlen_m;	// road length in meters
	private static double vRdlen_m;
	private static double rdlen; // (not rounded) road length in pixel units
	private static double vRdlen;
	private static double l_straight_m; // length(m) of straight sections of U-shape
	private static double l_vStraight_m; 
	private static int l_straight;	// length in pixel units
	private static int l_vStraight;
	private static double total_rmplen_m; // length of onramp + visible access road
	private static int lRamp;
	private static int lw; // lane width
	private static int r1, r2; // inner and outer radius
	private static int radius; // radius of curved road section
	private static int roadMargin; // space road-margin
	private static int xc, yc; // curve center
	private static int fcnr = 0;
	
	// level control variables
	private static int choice_Szen = CHOICE_SCEN_INIT;	// level code (corresponding to "Constants.java")
	private static double density;	// density
	private static double qIn;	// main inflow
	private static double perTr;	// truck fraction
	private static double v0_limit;	// speed limit
	private static double v0_limit_old = 0;	// previous speed limit
	private static double p_factor;	// politeness factor
	private static double qRamp;	// ramp inflow
	private static double p_factorRamp;	// ramp politeness factor
	private static double deltaB;	// lane changing threshold
	private static double deltaBRamp;	// ramp lane changing threshold
	static boolean traffLightTurnedGreen = false;	// traffic light in FOUR_WAY level not yet green
	
	static boolean traffLight2TurnedGreen = false;
	
	// time control variables
	private static double time = 0.0; // simulation time (reset to 0 in each "run")
	private static int itime=0; // accumulated simulation time steps
	private static int tsleep_ms;	// sleep time in milliseconds
	private static int tsleepFrame_ms;	// frame sleep time in milliseconds

	// vehicle information arrays
	static int imaxinit = (int)(0.001*RADIUS_M * 4 * Math.PI * DENS_MAX_INVKM + 300);
	static double[] pos = new double [imaxinit+1];
	static double[] speed = new double [imaxinit+1];
	static double[] lane = new double [imaxinit+1];
	static double[] lengthVeh = new double [imaxinit+1];
	static int [] nr = new int [imaxinit+1];
	static int [] old_i = new int [imaxinit+1];	// such that nr[i] = old_nr[old_i]
	static double[] old_pos = new double [imaxinit+1];
	static double[] posOn = new double [imaxinit+1];
	static double[] speedOn = new double [imaxinit+1];
	static double[] lengthVehOn = new double [imaxinit+1];
	
	static double[] pos2 = new double [imaxinit+1];
	static double[] speed2 = new double [imaxinit+1];
	static double[] lane2 = new double [imaxinit+1];
	static double[] lengthVeh2 = new double [imaxinit+1];
	static int [] nr2 = new int [imaxinit+1];
	static int [] old_i2 = new int [imaxinit+1];	// such that nr[i] = old_nr[old_i]
	static double[] old_pos2 = new double [imaxinit+1];
	static double[] posOn2 = new double [imaxinit+1];
	static double[] speedOn2 = new double [imaxinit+1];
	static double[] lengthVehOn2 = new double [imaxinit+1];
	
	// game objects
	private static Object microstreetLock = new Object();	// microstreetLock object
	private static MicroStreet microstreet;	// microstreet controller object
	private static OnRamp onramp;	// ramp controller object
	
	private static MicroStreet microstreet2;
	
	// color variables
	private final static int SIM_BG_COLOR = Color.rgb(128, 128, 128);	// gray tone
	private static int roadColor = Color.LTGRAY;	// light gray

	// game states
	public static final int STATE_PAUSE = 0;	// pause state
	public static final int STATE_RUNNING = 1;	// running state
	private static int mMode;	// indicates game mode
	private static boolean mRun = false;	// indicates thread should run?
	
	// miscellaneous
	private static Handler mHandler;	// handle to handler responsible for communicating score
	private static SurfaceHolder mSurfaceHolder;	// handle to SurfaceHolder
	private static int score;	// current score

	//	###############
	//		DetPosU
	//	###############
	
	// class for positioning of drawn object as function of position
	class DetPosU {
		// class variables
		public int x;
		public int y;

		// constructor
		public DetPosU(double pos){
			if(choice_Szen != FOUR_WAY){
			int intpos=(int)(pos*scale);

			if ((intpos > l_straight)&&(intpos<(rdlen-l_straight))){

				double arc = -0.5*(Math.PI)
						-(2*(Math.PI)*((double)(intpos-l_straight)))/
						((double)(2*(rdlen-2*l_straight)));

				double ca = Math.cos(arc);
				double sa = Math.sin(arc);


				x=(int)(xc + radius*ca);
				y=(int)(yc + radius*sa);
			}
			
			if (intpos <= l_straight){
				x = (int) xc+l_straight-intpos;
				y = (int) yc - radius;
			}
			
			if (intpos > (rdlen-l_straight)){

				x = (int) (xc+l_straight+intpos-rdlen);
				y = (int) (yc+radius);
			}
			}else{
				int intpos=(int)(pos*scale);
				x = (int) (intpos);
				y = (int) (yc+lw);
			}
		}
		
	}
	
	//	################
	//		PolyVehU
	//	################

	// class for rotating of drawn objects according to road position, etc.
	class PolyVehU {
		//class variables
		public double phi;
		double xc_car, yc_car;


		// following public vars temporarily for VehicleImage2D (martin oct11)
		public int xCenter;
		public int xPoints[] = new int[5];
		public int yCenter;
		public int yPoints[] = new int[5];

		// constructor
		public PolyVehU(double pos, double lane, double length, double width,
				int r1, int r2, int xc, int l_straight, int yc, int rdlen){

			double l = length*scale; // length of drawn car
			double w = width*scale; // width of drawn car
			double center_pos = pos + length/2; // in sim pos=back (!) end
			int intpos=(int)(center_pos*scale);
			double r = (lane>=0) ? r1 + (0.25+0.5*lane) * (r2-r1)
					: r1 + 0.5 * (r2-r1);
			phi=-Math.PI;

			if ((intpos>l_straight)&&(intpos<(rdlen-l_straight)) && choice_Szen != FOUR_WAY){

				double arc = -0.5*(Math.PI)
						-(2*(Math.PI)*((double)(intpos-l_straight)))/
						((double)(2*(rdlen-2*l_straight)));

				phi=arc-0.5*(Math.PI);
				double ca = Math.cos(arc); // cosine arc
				double sa = Math.sin(arc); // sin arc
				xc_car = (xc + r*ca);
				yc_car = (yc + r*sa);
				
				int x_frontleft = (int) (xc_car + 0.5*(-w*ca - l*sa));
				int x_frontright = (int) (xc_car + 0.5*( w*ca - l*sa));
				int x_backleft = (int) (xc_car + 0.5*(-w*ca + l*sa));
				int x_backright = (int) (xc_car + 0.5*( w*ca + l*sa));

				int y_frontleft = (int) (yc_car + 0.5*(-w*sa + l*ca));
				int y_frontright = (int) (yc_car + 0.5*( w*sa + l*ca));
				int y_backleft = (int) (yc_car + 0.5*(-w*sa - l*ca));
				int y_backright = (int) (yc_car + 0.5*( w*sa - l*ca));

				xPoints[0] = x_frontright;
				xPoints[1] = x_frontleft;
				xPoints[2] = x_backleft;
				xPoints[3] = x_backright;
				xPoints[4] = x_frontright;

				yPoints[0] = y_frontright;
				yPoints[1] = y_frontleft;
				yPoints[2] = y_backleft;
				yPoints[3] = y_backright;
				yPoints[4] = y_frontright;
			}
			
			if (intpos<=l_straight && choice_Szen != FOUR_WAY){

				phi=-Math.PI;
				double ca = 0.0;
				double sa = 1.0;

				xc_car = xc+l_straight-intpos;
				yc_car = yc - r;

				int x_frontleft = (int) (xc_car + 0.5*(-w*ca - l*sa));
				int x_frontright = (int) (xc_car + 0.5*( w*ca - l*sa));
				int x_backleft = (int) (xc_car + 0.5*(-w*ca + l*sa));
				int x_backright = (int) (xc_car + 0.5*( w*ca + l*sa));

				int y_frontleft = (int) (yc_car + 0.5*(-w*sa + l*ca));
				int y_frontright = (int) (yc_car + 0.5*( w*sa + l*ca));
				int y_backleft = (int) (yc_car + 0.5*(-w*sa - l*ca));
				int y_backright = (int) (yc_car + 0.5*( w*sa - l*ca));

				xPoints[0] = x_frontright;
				xPoints[1] = x_frontleft;
				xPoints[2] = x_backleft;
				xPoints[3] = x_backright;
				xPoints[4] = x_frontright;

				yPoints[0] = y_frontright;
				yPoints[1] = y_frontleft;
				yPoints[2] = y_backleft;
				yPoints[3] = y_backright;
				yPoints[4] = y_frontright;
			}

			if (intpos>(rdlen-l_straight)){
	
				phi=0;
				double ca = 0.0;
				double sa = -1.0;

				xc_car = xc+l_straight+intpos-rdlen;
				yc_car = yc+r;
				if(choice_Szen == FOUR_WAY){
					xc_car = l_straight+intpos-rdlen;
					yc_car = 2*yc-r-roadMargin;
				} 
				if(rdlen == vRdlen){
					xc_car = (RELPOS_TL * rdlen_m)* scale+lw - r1 + r;
					yc_car = intpos;
					ca = -1.0;
					sa = 0.0;
					phi=-Math.PI*3/2;
				}
				int x_frontleft = (int) (xc_car + 0.5*(-w*ca - l*sa));
				int x_frontright = (int) (xc_car + 0.5*( w*ca - l*sa));
				int x_backleft = (int) (xc_car + 0.5*(-w*ca + l*sa));
				int x_backright = (int) (xc_car + 0.5*( w*ca + l*sa));

				int y_frontleft = (int) (yc_car + 0.5*(-w*sa + l*ca));
				int y_frontright = (int) (yc_car + 0.5*( w*sa + l*ca));
				int y_backleft = (int) (yc_car + 0.5*(-w*sa - l*ca));
				int y_backright = (int) (yc_car + 0.5*( w*sa - l*ca));

				xPoints[0] = x_frontright;
				xPoints[1] = x_frontleft;
				xPoints[2] = x_backleft;
				xPoints[3] = x_backright;
				xPoints[4] = x_frontright;

				yPoints[0] = y_frontright;
				yPoints[1] = y_frontleft;
				yPoints[2] = y_backleft;
				yPoints[3] = y_backright;
				yPoints[4] = y_frontright;
			}
			
			xCenter=(int)(xc_car);
			yCenter=(int)(yc_car);
		}
	}
	
	//	####################
	//		Constructors
	//	####################

	// SimThread default constructor: takes advantage of static class variables
	public SimThread() {}

	// SimThread alternative constructor: sets class variables and initializes SimThread
	public SimThread(SurfaceHolder surfaceHolder,
			Handler handler, int choice_Szen, double density, double qIn, double perTr,
			double p_factor, double deltaB,
			double p_factorRamp, double deltaBRamp,
			int tsleep_ms) {
		
		mSurfaceHolder = surfaceHolder;	// set handle to SurfaceHolder
		mHandler = handler;	// set handle to Handler

		// initialize game variables
		newValues(choice_Szen, density, qIn, perTr, 33,
				p_factor, deltaB, tsleep_ms);
		
		SimThread.p_factorRamp = p_factorRamp;	// set politeness factor
		SimThread.deltaBRamp = deltaBRamp;	// set changing threshold for ramp
		tsleepFrame_ms = 10;	// specify desired frame sleep time in milliseconds

		setState(STATE_PAUSE);	// pause game
	}

	// handles starting of game
	public void doStart() {
		synchronized (mSurfaceHolder) {	// lock SurfaceHolder
			setState(STATE_RUNNING);	// run game
		}
	}

	// handles drawing of lane lines
	private void drawLines(Canvas g){
		int lineLength = (int)(scale*LINELENGTH_M);	// calculate line length
		int lineWidth = 1;	// set line width
		int numberOfLines = (int)(rdlen/(scale*(LINELENGTH_M+GAPLENGTH_M)));	// calculate number of lines;		
		if(choice_Szen == FOUR_WAY){
		int numberOfLinesV = (int)(vRdlen / (scale*(LINELENGTH_M+GAPLENGTH_M))); // <-------------
		for (int i=0;i<=numberOfLinesV; i++){  // <----------------
			double pos_m = i*(LINELENGTH_M+GAPLENGTH_M);
			PolyVehU roadLine = new PolyVehU
					(pos_m, -1, lineLength, lineWidth, r1, r2,
							xc, l_vStraight, 0, (int)(vRdlen));
			GraphicsUtil.fillPolygon(g, roadLine.xPoints, roadLine.yPoints, 4, Color.WHITE);
		}
		}
		
		for (int i=0; i<numberOfLines; i++) {	// for every line
			double pos_m = i*(LINELENGTH_M+GAPLENGTH_M);	// get position of line
			// orient line appropriately
			PolyVehU roadLine = new PolyVehU
					(pos_m, -1, lineLength, lineWidth, r1, r2,
							xc, l_straight, yc, (int)(rdlen));
			GraphicsUtil.fillPolygon(g, roadLine.xPoints, roadLine.yPoints, 4, Color.WHITE);	// draw line at specified location in white
		}
	}

	//	###############
	//		Getters
	//	###############
	
	// returns politeness factor
	public double getPolitenessFactor() {
		return choice_Szen != ON_RAMP ? p_factor : p_factorRamp;	// return politeness factor for appropriate level code (corresponding to "Constants.java")
	}

	// returns scale after setting
	public double getScale(){
		setScales();	// set scales
		return scale;	// return scale
	}

	// returns score
	public int getScore() {
		return score;	// return score
	}

	// returns speed limit
	public double getSpeedLimit() {
		return v0_limit;	// return speed limit
	}

	//	################################
	//		SimThread Member Methods
	//	################################
	
	// imposes the specified speed limit on the microstreet and onramp objects
	private void imposeSpeedLimit(){
		if (microstreet == null) return;	// return if no microstreet defined

		v0_limit_old = v0_limit;	// set old speed limit to current speed limit

		// calculate speed limit for trucks
		double v0trucks = (v0_limit<VMAX_TRUCK_KMH/3.6)
				? v0_limit
						: VMAX_TRUCK_KMH/3.6;

		if ((choice_Szen==LANE_CLOSURE)||(choice_Szen==FOUR_WAY)){	// for LANE_CLOSURE and FOUR_WAY levels
			( (IDM)(microstreet.idmCar)).set_v0(v0_limit);	// set car speed limit
			( (IDM)(microstreet.idmCar)).initialize();	// initialize car object
			( (IDM)(microstreet.idmTruck)).set_v0(v0trucks);	// set truck speed limit
			( (IDM)(microstreet.idmTruck)).initialize();	// initialze truck object
			
			if(choice_Szen==FOUR_WAY){
				if(microstreet2==null)return;
				( (IDM)(microstreet2.idmCar)).set_v0(v0_limit);	// set car speed limit
				( (IDM)(microstreet2.idmCar)).initialize();	// initialize car object
				( (IDM)(microstreet2.idmTruck)).set_v0(v0trucks);	// set truck speed limit
				( (IDM)(microstreet2.idmTruck)).initialize();	// initialze truck object
			}
		}
		
		else if (choice_Szen==ON_RAMP) {	// for ON_RAMP level
			( (IDM)(microstreet.idmCar)).set_v0(v0_limit);	// set car speed limit
			( (IDM)(microstreet.idmCar)).initialize();	// initialize car object
			( (IDM)(microstreet.idmTruck)).set_v0(v0trucks);	// set truck speed limit
			( (IDM)(microstreet.idmTruck)).initialize();	// initialize truck object
			
			if (onramp == null) return;	// return if no onramp defined
			( (IDM)(onramp.idmCar)).set_v0(v0_limit);	// set car speed limit for ramp
			( (IDM)(onramp.idmCar)).initialize();	// initialize car object for ramp
			( (IDM)(onramp.idmTruck)).set_v0(v0trucks);	// set truck speed limit for ramp
			( (IDM)(onramp.idmTruck)).initialize();	// initialize truck object for ramp
		}
		
		else if(choice_Szen==UPHILL){	// for UPHILL level
			( (IDM)(microstreet.sync1Car)).set_v0(v0_limit);	// set sync car speed limit
			( (IDM)(microstreet.sync1Car)).initialize();	// initialize sync car object
			( (IDM)(microstreet.sync1Truck)).set_v0(v0trucks);	// set sync truck speed limit
			( (IDM)(microstreet.sync1Truck)).initialize();	// initialize sync truck object
		}

	}

	// returns whether SimCanvas has been scaled or not
	private boolean isScaled(){
		return mCanvasHeight != 1 && mCanvasWidth != 1;	// return isScaled
	}

	// handles setting of traffic simulation variables for all levels except ON_RAMP
	public void newValues (int choice_Szen,
			double density, double qIn, double perTr,
			double v0_limit, double p_factor, double deltaB,
			int tsleep_ms) {

		SimThread.choice_Szen=choice_Szen;	// set level code
		SimThread.density = density;	// set density
		SimThread.qIn = qIn;	// set main inflow
		SimThread.perTr = perTr;	// set truck fraction
		SimThread.v0_limit = v0_limit;	// set speed limit
		SimThread.p_factor = p_factor;	// set politeness factor
		SimThread.deltaB = deltaB;	// set lane changing threshold
		SimThread.tsleep_ms = tsleep_ms;	// set sleep time in milliseconds

		setScales();	// set scales
		rdlen_m = ((choice_Szen!=FOUR_WAY)?Math.PI *RADIUS_M + 2 * l_straight_m : l_straight_m);	// calculate road length in meters
		traffLightTurnedGreen = false;	// set traffic light not turned green yet
		traffLight2TurnedGreen = false;
		// reset time variables
		time = 0.0;
		itime = 0;

	}

	// handles setting of traffic simulation variables for ON_RAMP level only
	public void newValues2 (double qIn, double perTr,
			double p_factor, double deltaB,
			double qRamp, double p_factorRamp,
			double deltaBRamp, int tsleep_ms, double v0_limit) {


		SimThread.choice_Szen=ON_RAMP; // only to be called for ON_RAMP level
		setScales(); // set scales
		
		SimThread.qIn = qIn;	// set main inflow
		SimThread.perTr = perTr;	// set truck fraction
		SimThread.p_factor = p_factor;	// set politeness factor
		SimThread.v0_limit = v0_limit;	// set speed limit
		SimThread.qRamp = qRamp;	// set ramp inflow
		SimThread.p_factorRamp = p_factorRamp;	// set ramp politeness factor
		SimThread.deltaB = deltaB;	// set lane changing threshold
		SimThread.deltaBRamp = deltaBRamp;	// set ramp lane changing threshold
		SimThread.tsleep_ms = tsleep_ms;	// set sleep time in milliseconds
		
		rdlen_m =((choice_Szen!=FOUR_WAY)?Math.PI *RADIUS_M + 2 * l_straight_m : l_straight_m);	// calculate road length in meters
		
		//reset time variables
		time = 0.0;
		itime = 0;
	}

	// handles control of drawing
	private void paintSim(Canvas g){
		synchronized(microstreetLock) {
			prepareVehData();	// first prepare vehicle data
			paintU(g);	// then paint U-shaped road
			updateU(g);	// then update U-shaped road

			paintSymbols(g);	// paint road signs/symbols
			if (v0_limit != v0_limit_old) imposeSpeedLimit();	//impose speed limit if applicable
		}
	}

	// handles drawing of road signs/symbols
	private void paintSymbols(Canvas g){

		if (choice_Szen==FOUR_WAY) { // if FOUR_WAY level, draw traffic light
			int heightTL = (int)(radius*0.16); // calculate total height of light
			int radiusTL = (int)(radius*0.024); // calculate radius of lights
			double det1pos = RELPOS_TL * rdlen_m;	// calculate position of light
			double det2pos = RELPOS_TL2 * rdlen_m; // <--- new Raul
			
			int detx=(new DetPosU(det1pos)).x ;	// calculate x position to draw at
			int dety=(new DetPosU(det1pos)).y - lw;	// calculate y position to draw at
			int detx2=(new DetPosU(det2pos)).x ;	// <--- new Raul
			int dety2=(new DetPosU(det2pos)).y - lw;	// <--- new Raul
			
			GraphicsUtil.fillRect(g, detx, dety-heightTL, 2, heightTL, Color.BLACK);	// draw pole in black
			GraphicsUtil.fillRect(g, detx-radiusTL, dety-heightTL, 2*radiusTL, 4*radiusTL, Color.BLACK);	// draw light box in black
			GraphicsUtil.fillRect(g, detx2, dety2-heightTL, 2, heightTL, Color.BLACK);	// <--- new Raul
			GraphicsUtil.fillRect(g, detx2-radiusTL, dety2-heightTL, 2*radiusTL, 4*radiusTL, Color.BLACK);	// <--- new Raul
			
			if (time < T_GREEN/2) { // if red light
				GraphicsUtil.fillOval(g, detx-radiusTL, dety-heightTL, 2*radiusTL, 2*radiusTL, Color.RED);	// draw red light
			}
			else {	// if green light
				GraphicsUtil.fillOval(g, detx-radiusTL, dety-heightTL+2*radiusTL, 2*radiusTL, 2*radiusTL, Color.GREEN);	// draw green light
			}
			
			if (time < T_GREEN) { // if red light
				GraphicsUtil.fillOval(g, detx2-radiusTL, dety2-heightTL, 2*radiusTL, 2*radiusTL, Color.RED);	// <--- new Raul
			}
			else {	// if green light
				GraphicsUtil.fillOval(g, detx2-radiusTL, dety2-heightTL+2*radiusTL, 2*radiusTL, 2*radiusTL, Color.GREEN);	// <--- new Raul
			}
			
		}

		if (choice_Szen==UPHILL) { // if UPHILL level, draw gradation sign
			double detpos =l_straight_m + 0.5 * Math.PI * RADIUS_M;	// calculate position of sign

			// position of pole
			int heightTot = (int)(radius*0.24); // calculate total height of sign and pole
			int heightSign = (int)(radius*0.16); // calculate height of sign
			
			int x0=(new DetPosU(detpos)).x + (int)(0.2*radius);	// calculate x position to draw at for pole
			int y0=(new DetPosU(detpos)).y + heightTot;	// calculate y position to draw at for pole
			
			int xc = x0; // copy pole x position to sign x position
			int yc = y0-heightTot + (int)(0.5*heightSign);	// calculate y position to draw at for sign
			
			double relWidthRed = 0.2; // set desired relative red width

			GraphicsUtil.fillRect(g, x0, y0-heightTot, 2, heightTot, Color.BLACK);	// draw pole in black

			// draw triangular sign
			int hbasis = (int)(0.577 * heightSign); // tan(30 degrees)
			int vbasis = (int)(0.5 * heightSign);
			int[] ptsx = {xc,xc-hbasis,xc+hbasis};
			int[] ptsy = {yc-vbasis, yc+vbasis, yc+vbasis};
			GraphicsUtil.fillPolygon(g, ptsx, ptsy, ptsx.length, Color.RED);	// draw sign in red

			yc += (int)(0.3*relWidthRed*vbasis);
			hbasis = (int)((1-relWidthRed)*hbasis);
			vbasis = (int)((1-relWidthRed)*vbasis);
			int[] ptsx1 = {xc,xc-hbasis,xc+hbasis};
			int[] ptsy1 = {yc-vbasis, yc+vbasis, yc+vbasis};
			GraphicsUtil.fillPolygon(g, ptsx1, ptsy1, ptsx1.length, Color.WHITE);	// draw white part of sign

			// draw grade
			ptsx1[0] = (int)(0.5*(ptsx[0]+ptsx[2]));
			ptsy1[0] = (int)(0.5*(ptsy[0]+ptsy[2]));
			GraphicsUtil.fillPolygon(g, ptsx1, ptsy1, ptsx1.length, Color.BLACK);	// draw grade part of sign in black

		}

	}

	// handles drawing of U-shaped road
	private void paintU(Canvas g){
		
		GraphicsUtil.fillRect(g, 0, 0, xsize, ysize, SIM_BG_COLOR);	// draw background color
		if (choice_Szen!= FOUR_WAY) {
		// draw road curve
		GraphicsUtil.fillOval(g, xc-r2, yc-r2, 2*r2, 2*r2, roadColor);
		GraphicsUtil.fillOval(g, xc-r1, yc-r1, 2*r1, 2*r1, SIM_BG_COLOR);

		// draw road
		GraphicsUtil.fillRect(g, xc, 0, xsize, ysize, SIM_BG_COLOR); // commenting out draws whole circle
		GraphicsUtil.fillRect(g, xc, yc-r2, l_straight, 2*lw, roadColor);
		GraphicsUtil.fillRect(g, xc, yc+r1, l_straight, 2*lw, roadColor);

		
		}else {
		GraphicsUtil.fillRect(g, 0, yc, l_straight, 2*lw, roadColor); // <--- new Raul
		GraphicsUtil.fillRect(g, (int)((RELPOS_TL * rdlen_m)* scale)+lw, 0, 2*lw, l_vStraight, roadColor); // <--- vertical road
		}
		if (choice_Szen==ON_RAMP) { // if ON_RAMP level
			// draw onramp
			GraphicsUtil.fillRect(g, 0, yc+r2, xc+lRamp, lw, roadColor);
			int[] poly_x = {xc+lRamp,xc+lRamp,xc+lRamp+2*lw};
			int[] poly_y = {yc+r2,yc+r2+lw,yc+r2};
			GraphicsUtil.fillPolygon(g, poly_x, poly_y, poly_x.length, roadColor);
		}

		drawLines(g);	// draw road lines
	}

	// handles pausing of game
	public void pause() {
		synchronized (mSurfaceHolder) {	// lock SurfaceHolder
			if (mMode == STATE_RUNNING) {	// if previously running
				setState(STATE_PAUSE);	// pause game
				microstreet = null;	// clear microstreet
				onramp = null;	// clear onramp
				microstreet2=null;
			}
		}
	}

	@SuppressWarnings("rawtypes") // handles preparation of vehicle data
	private void prepareVehData(){
		if (microstreet == null) return;	// return if microstreet undefined

		Vector positions=microstreet.positions;
		Vector velocities=microstreet.velocities;
		Vector numbers=microstreet.numbers;
		Vector lanes=microstreet.realLanes;
		Vector lengths=microstreet.lengths;

		Vector old_positions=microstreet.old_pos;
		Vector old_numbers=microstreet.old_numbers;

		int imax = positions.size()-1;
		int imaxOn=0;

		if (choice_Szen==ON_RAMP && onramp != null) imaxOn=onramp.positions.size()-1;

		for (int i=imax; i>=0; i--){
			pos[i] = ((Double) positions.elementAt(i)).doubleValue();
			speed[i] = ((Double) velocities.elementAt(i)).doubleValue();
			lane[i] = ((Double) lanes.elementAt(i)).doubleValue();
			lengthVeh[i] = ((Double) lengths.elementAt(i)).doubleValue();
			nr[i] = ((Integer) numbers.elementAt(i)).intValue();

			// determine vector index of same car in the past time step
			old_i[i] = i;
			int old_nr = ((Integer) old_numbers.elementAt(i)).intValue();
			if (old_nr != nr[i]) { // overtaking during last step -> changed pos
				old_i[i] = (i+1<=imax) ? (i+1) : 0; // old vehicle number at i+1?
				old_nr = ((Integer) old_numbers.elementAt(old_i[i])).intValue();

				if(old_nr!=nr[i]) { // veh index not at i+1 -> must be at i-1
					old_i[i] = (i-1>=0) ? (i-1) : imax;
					old_nr = ((Integer) old_numbers.elementAt(old_i[i])).intValue();
				}
			}

			if (old_nr != nr[i]){ // not yet ordered?
				for (int j=2; ((j<=5)&&(old_nr != nr[i])); j++){
					old_i[i] = (i+j<=imax) ? (i+j) : 0;
					old_nr = ((Integer) old_numbers.elementAt(old_i[i])).intValue();
				}
				
				if(old_nr!=nr[i]){
					for (int j=-2; ((j>=-5)&&(old_nr != nr[i])); j--){
						old_i[i] = (i+j>=0) ? (i+j) : imax;
						old_nr =((Integer)old_numbers.elementAt(old_i[i])).intValue();
					}
				}
			}

			old_pos[i] = ((Double) old_positions.elementAt(old_i[i])).doubleValue();
		}

		// onramp data
		if (choice_Szen==ON_RAMP && onramp != null){
			for (int i=imaxOn; i>=0; i--){
				posOn[i] = ((Double) onramp.positions.elementAt(i)).doubleValue();
				lengthVehOn[i]=((Double) onramp.lengths.elementAt(i)).doubleValue();
			}
		}

		if(choice_Szen == FOUR_WAY && microstreet != null){
			if (microstreet == null) return;	// return if microstreet undefined

			Vector positions2=microstreet2.positions;
			Vector velocities2=microstreet2.velocities;
			Vector numbers2=microstreet2.numbers;
			Vector lanes2=microstreet2.realLanes;
			Vector lengths2=microstreet2.lengths;

			Vector old_positions2=microstreet2.old_pos;
			Vector old_numbers2=microstreet2.old_numbers;

			int imax2 = positions2.size()-1;
			//int imaxOn=0;

			//if (choice_Szen==ON_RAMP && onramp != null) imaxOn=onramp.positions.size()-1;

			for (int i=imax2; i>=0; i--){
				pos2[i] = ((Double) positions2.elementAt(i)).doubleValue();
				speed2[i] = ((Double) velocities2.elementAt(i)).doubleValue();
				lane2[i] = ((Double) lanes2.elementAt(i)).doubleValue();
				lengthVeh2[i] = ((Double) lengths2.elementAt(i)).doubleValue();
				nr2[i] = ((Integer) numbers2.elementAt(i)).intValue();

				// determine vector index of same car in the past time step
				old_i2[i] = i;
				int old_nr2 = ((Integer) old_numbers2.elementAt(i)).intValue();
				if (old_nr2 != nr2[i]) { // overtaking during last step -> changed pos
					old_i2[i] = (i+1<=imax2) ? (i+1) : 0; // old vehicle number at i+1?
					old_nr2 = ((Integer) old_numbers2.elementAt(old_i2[i])).intValue();

					if(old_nr2!=nr2[i]) { // veh index not at i+1 -> must be at i-1
						old_i2[i] = (i-1>=0) ? (i-1) : imax;
						old_nr2 = ((Integer) old_numbers2.elementAt(old_i2[i])).intValue();
					}
				}

				if (old_nr2 != nr2[i]){ // not yet ordered?
					for (int j=2; ((j<=5)&&(old_nr2 != nr2[i])); j++){
						old_i2[i] = (i+j<=imax2) ? (i+j) : 0;
						old_nr2 = ((Integer) old_numbers2.elementAt(old_i2[i])).intValue();
					}
					
					if(old_nr2!=nr2[i]){
						for (int j=-2; ((j>=-5)&&(old_nr2 != nr2[i])); j--){
							old_i2[i] = (i+j>=0) ? (i+j) : imax2;
							old_nr2 =((Integer)old_numbers2.elementAt(old_i2[i])).intValue();
						}
					}
				}

				old_pos2[i] = ((Double) old_positions2.elementAt(old_i2[i])).doubleValue();
			}

		}
	}
	
	@Override	// specifies procedure to run for SimThread
	public void run() {
		while (mRun) {	// while game is running
			Canvas c = null;	// declare Canvas object
			try {
				c = mSurfaceHolder.lockCanvas(null);	// try to get handle to SurfaceHolder canvas
				synchronized (mSurfaceHolder) {	// lock SurfaceHolder
					if (mMode == STATE_RUNNING && isScaled()) updatePhysics();	// if is scaled and is running, update physics
					paintSim(c); // paint simulation
				}
			} finally {	// in all cases
				if (c != null) {
					mSurfaceHolder.unlockCanvasAndPost(c);	// unlock and post canvas to SurfaceHolder
				}
			}
		}
	}

	//	###############
	//		Setters
	//	###############
	
	// sets running state of game
	public void setRunning(boolean b) {
		mRun = b; // set running game
	}

	// sets the scales and calculates size of road objects
	public void setScales() {
		if (!isScaled()) return;	// return if not scaled
		
		xsize = mCanvasWidth;	// set x size to canvas width
		ysize = mCanvasHeight;	// set y size to canvas height
		
		if (xsize<10) xsize=10; // specify minimum x size
		if (ysize<10) ysize=10; // specify minimum y size

		double xsize_m= 500;	// set desired x size in meters
		double ysize_m= 500;	// set desired y size in meters

		// calculate x and y size according to level and road size
		xsize_m = 2 * RADIUS_M;
		ysize_m = 2*RADIUS_M + ((choice_Szen==ON_RAMP) ? 3 : 2) * LANEWIDTH_M;
		xsize_m *= (1+2*REL_ROAD_MARGIN);
		ysize_m *= (1+2*REL_ROAD_MARGIN);
		
		scalex = xsize/xsize_m;	// set x scale
		scaley = ysize/ysize_m;	// set y scale
		scale = scalex < scaley ? scalex : scaley;	// set scale to min(x scale, y scale)

		// calculate size of road segments
		l_straight_m = xsize/scale - ((choice_Szen!=FOUR_WAY)?RADIUS_M : 0);
		l_vStraight_m = ysize/scale; // <---------------
		rdlen_m = ((choice_Szen!=FOUR_WAY)?Math.PI *RADIUS_M + 2 * l_straight_m : l_straight_m);
		vRdlen_m = l_vStraight_m; // <------------------
		total_rmplen_m = RADIUS_M * (1+REL_ROAD_MARGIN) +L_RAMP_M;
		rdlen = rdlen_m * scale;
		vRdlen = vRdlen_m * scale; // <------------------
		lRamp = (int)(L_RAMP_M * scale);
		l_straight = (int)(l_straight_m * scale);
		l_vStraight = (int) (l_vStraight_m * scale); // <---------
		roadMargin = (int)(REL_ROAD_MARGIN*ysize);
		lw = (int) (LANEWIDTH_M * scale); // Lane width
		radius = (int)(RADIUS_M*scale);
		r1 = radius-lw; // inner radius
		r2 = radius+lw; // outer radius
		xc = roadMargin + lw + radius; // center of circle or semicircle
		yc = roadMargin + lw + radius;
	
		start(choice_Szen, density);	// start level with density
	}

	// sets desired game state
	public void setState(int mode) {
		synchronized (mSurfaceHolder) {	// lock SurfaceHolder
			mMode = mode;	// set game state
		}
	}

	// sets Surface size
	public void setSurfaceSize(int width, int height) {
		synchronized (mSurfaceHolder) {	// lock SurfaceHolder
			mCanvasWidth = width;	// set canvas width
			mCanvasHeight = height;	// set canvas height

			setScales();	// set scales
		}
	}

	// handles starting of level with desired density
	public void start(int choice_Szen, double density){
		// create new microstreet object
		microstreet=new MicroStreet(
				rdlen_m, density,p_factor,deltaB,fcnr,choice_Szen, this);
		
		imposeSpeedLimit();	// impose speed limit
		
		if(choice_Szen==ON_RAMP) {	// if ON_RAMP level
			double mergingpos =  Math.PI * RADIUS_M + l_straight_m + 0.5*L_RAMP_M;	// calculate merge position
			// create new onramp object
			onramp=new OnRamp (microstreet,
					total_rmplen_m,
					L_RAMP_M, mergingpos, p_factorRamp, deltaBRamp);
		}
		if(choice_Szen==FOUR_WAY){
			microstreet2=new MicroStreet(
					vRdlen_m, density,p_factor,deltaB,fcnr,choice_Szen, this);
			microstreet.setTL(RELPOS_TL);
			microstreet2.setTL(RELPOS_TL2);
		}	
	}

	// handles update of street objects
	private void updatePhysics() {
		try {
			Thread.sleep(tsleep_ms);	// sleep for desired time in milliseconds
		}catch (InterruptedException e) {}

		synchronized(microstreetLock) {	// lock microstreetObject
			if ((time>T_GREEN/2)&&(choice_Szen==FOUR_WAY)	// if FOUR_WAY level and light needs turning
					&&(traffLightTurnedGreen==false)){
				microstreet.open(2); // open microstreet
				traffLightTurnedGreen=true; // turn light green
			}else if ((time>T_GREEN)&&(choice_Szen==FOUR_WAY)	// if FOUR_WAY level and light needs turning
					&&(traffLight2TurnedGreen==false)){
				microstreet.open(0); // open microstreet
				traffLight2TurnedGreen=true; // turn light green
			}
			
			// update microstreet
			microstreet.update(time, TIMESTEP_S, choice_Szen,
					density, qIn, perTr, p_factor, deltaB);
			
			if (choice_Szen==ON_RAMP) {	// if ON_RAMP level
				onramp.update(time,TIMESTEP_S,qRamp,perTr, p_factorRamp, deltaBRamp);	// update onramp
			}
			
			if (choice_Szen == FOUR_WAY){
				microstreet2.update(time, TIMESTEP_S, choice_Szen,
						density, qIn, perTr, p_factor, deltaB);
			}

			// set time variables as appropriate
			time += TIMESTEP_S;
			itime++;         
			tsleepFrame_ms = (int)(0.5 * tsleep_ms + 0.5);
			if (tsleepFrame_ms < 2) tsleepFrame_ms = 2;
		}
	}

	@SuppressWarnings("rawtypes")	// handles drawing of U-shaped road
	private void updateU(Canvas g){
		if (microstreet == null) return; // return if microstreet undefined
		
		Vector colors=microstreet.colors;	// get colors
		Vector colors2 = microstreet.colors; //<----
		int imax = colors.size()-1;
		
		// draw microstreet vehicles
		for (int i=imax; i>=0; i--) {
			// get appropriate orientation for vehicle
			PolyVehU pvu = new PolyVehU( pos[i],lane[i],
					lengthVeh[i], VEH_WIDTH_M, r1, r2, xc,
					l_straight, yc, (int) rdlen);

			int vehColor = (Integer)colors.elementAt(i);	// get vehicle color

			// paint vehicle polygon at new position
			String vehType=(lengthVeh[i]<8) ? "car" : "truck";	// get vehicle type
			if (vehColor == Color.WHITE) vehType="obstacle";
			VehicleImage2D vehimg=new VehicleImage2D(lengthVeh[i],VEH_WIDTH_M, vehType, false, vehColor, scale);	// get vehicle image
			vehimg.setPositionPix(pvu.xCenter,pvu.yCenter);	// position vehicle
			vehimg.setDirection(pvu.phi);	// set angle
			vehimg.setSpeed(speed[i]);	// set speed
			vehimg.draw(g,1);	// draw vehicle
		}


		// draw microstreet vehicles on verticle road
		if(choice_Szen==FOUR_WAY){
			if(microstreet2==null)return;
		for (int i=imax; i>=0; i--) {
			// get appropriate orientation for vehicle
			PolyVehU pvu = new PolyVehU( pos2[i],lane2[i],
					lengthVeh2[i], VEH_WIDTH_M, r1, r2, xc,
					l_vStraight, 0, (int) vRdlen);

			int vehColor = (Integer)colors2.elementAt(i);	// get vehicle color

			// paint vehicle polygon at new position
			String vehType=(lengthVeh2[i]<8) ? "car" : "truck";	// get vehicle type
			if (vehColor == Color.WHITE) vehType="obstacle";
			VehicleImage2D vehimg=new VehicleImage2D(lengthVeh2[i],VEH_WIDTH_M, vehType, false, vehColor, scale);	// get vehicle image
			vehimg.setPositionPix(pvu.xCenter,pvu.yCenter);	// position vehicle
			vehimg.setDirection(pvu.phi);	// set angle
			vehimg.setSpeed(speed2[i]);	// set speed
			vehimg.draw(g,1);	// draw vehicle
		}
		}
		
		// draw onramp vehicles
		if(choice_Szen==ON_RAMP && onramp != null) {	// if ON_RAMP level
			int imaxOn = onramp.colors.size()-1;
			
			for (int i=imaxOn; i>=0; i--){
				String vehType=(lengthVehOn[i]<8) ? "car" : "truck";	// get vehicle type
				int vehColor=(Integer)(onramp.colors.elementAt(i));	// get vehicle color
				double speed=(Double)(onramp.velocities.elementAt(i));	// get vehicle speed
				int xc_veh=xc +lRamp
						+ (int)(scale*(0.5*lengthVehOn[i]+posOn[i] - onramp.roadLength ));	// get center x position
				int yc_veh=yc+r2+(int)(0.25*lw+scale*VEH_WIDTH_M);	// get center y position
				VehicleImage2D vehimg=new VehicleImage2D(lengthVehOn[i],VEH_WIDTH_M,	// get vehicle image
						vehType, false, vehColor, scale);
				vehimg.setPositionPix(xc_veh,yc_veh);	// position vehicle
				vehimg.setDirection(0);	// set angle
				vehimg.setSpeed(speed);	// set speed
				vehimg.draw(g,1);	// draw vehicle
			}
		}

		if ( itime < 2){	// when appropriate
			drawLines(g);	// draw lines
		}

		try{
			Thread.sleep(tsleepFrame_ms);	// sleep for desired frame sleep time
		} catch(InterruptedException e) {}

	}
	
	@Override	// handles onScoreChange events
	public void onScoreChange(int score) {
		SimThread.score = score;	// set score
		Message msg = mHandler.obtainMessage();	// obtain message
		Bundle b = new Bundle();	// create new Bundle
		b.putInt("score", score);	// put score in bundle
		msg.setData(b);	// put bundle in message
		mHandler.sendMessage(msg);	// send message to handler
	}
	
	
}